package com.hifar.test.oaApi;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import com.hifar.test.oaApi.common.ApiMarshaller;
import com.hifar.test.oaApi.common.ApiResponse;
import com.hifar.test.oaApi.common.ApiUtils;
import com.hifar.test.oaApi.common.MarshallerUtil;

public class OpenApiClient {

    // 编码方式
    public static final String ALGORITHM_HMAC = "HmacSHA1";
    public static final String ALGORITHMHMAC__MD5 = "HmacMD5";
    public static final String ALGORITHM_MD5 = "MD5";

    public static final String FORMAT_JSON = "json";
    public static final String FORMAT_XML = "xml";
    private String serverUrl;
    private String accessKey;
    private String secret;
    private String format = "json";
    private int connectTimeout = 3000;
    private int readTimeout = 15000;
    private static Map<Class<?>, ApiConverter<?>> converterMap = new HashMap<Class<?>, ApiConverter<?>>();
    private static boolean debug = false;

    static {
        converterMap.put(Map.class, new MapConverter());
    }

    public OpenApiClient(String serverUrl, String accessKey, String secret) {
        this.serverUrl = serverUrl;
        this.accessKey = accessKey;
        this.secret = secret;
    }

    public OpenApiClient(String serverUrl, String accessKey, String secret, String format) {
        this(serverUrl, accessKey, secret);
        this.format = format;
    }

    public OpenApiClient(String serverUrl, String accessKey, String secret, String format, int connectTimeout, int readTimeout) {
        this(serverUrl, accessKey, secret, format);
        this.connectTimeout = connectTimeout;
        this.readTimeout = readTimeout;
    }

    public String exec(String cmd, Map<String, Object> params) throws ApiException {
        return exec(cmd, params, Collections.EMPTY_LIST);
    }

    public String exec(String cmd, Map<String, Object> params, Collection<String> ignoreSign) throws ApiException {
        Map<String, String> t = new HashMap<String, String>();
        if (params != null) {
            for (Entry<String, Object> entry : params.entrySet()) {
                Class<?> type = entry.getValue().getClass();
                ApiConverter convertor = getConvertor(type);
                String key = entry.getKey();
                Object fieldValue = entry.getValue();
                if (fieldValue != null) {
                    if (convertor != null) {
                        String strParamValue = convertor.convert(fieldValue, FORMAT_JSON);
                        t.put(key, strParamValue);
                    } else if (type.isAnnotationPresent(XmlRootElement.class) || type.isAnnotationPresent(XmlType.class) || isCollectionOrArray(type) || Map.class.isAssignableFrom(type)) {
                        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
                        getMarshaller(FORMAT_JSON).marshaller(fieldValue, bos);
                        try {
                            t.put(key, bos.toString("utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            throw new ApiException(e);
                        }
                    } else {
                        t.put(key, fieldValue.toString());
                    }
                }
            }
        }

        t.put(ApiUtils.TIMESTAMP, Long.toString(System.currentTimeMillis()));
        t.put(ApiUtils.CMD, cmd);
        t.put(ApiUtils.FORMAT, this.format);
        t.put(ApiUtils.ACCESS_KEY, this.accessKey);
        t.put(ApiUtils.SIG_METHOD, ALGORITHMHMAC__MD5);
        String sig = ApiUtils.makeSig(t, this.secret, ignoreSign);
        t.put(ApiUtils.SIG, sig);

        String charset = ApiUtils.CHARSET_UTF8;
        String ctype = "application/x-www-form-urlencoded;charset=" + charset;
        String query = ApiUtils.buildQuery(t, charset);
        if (debug) {
            System.out.println("sig:" + sig + " , query:" + query);
        }
        byte[] content = {};
        if (query != null) {
            content = ApiUtils.convertBytes(query);
        }
        try {
            return _doPost(this.serverUrl, ctype, content, connectTimeout, readTimeout, null);
        } catch (Exception e) {
            throw new ApiException(e);
        }
    }

    private static boolean isCollectionOrArray(Class<?> type) {
        return (Collection.class.isAssignableFrom(type)) || (type.isArray());
    }

    public <T> T exec(String cmd, Map<String, Object> params, Class<T> apiResponse) {
        String res = exec(cmd, params);
        ApiMarshaller apiMarshaller = getMarshaller(this.format);
        T t = apiMarshaller.unmarshaller(res, apiResponse);
        if (t instanceof ApiResponse) {
            ApiResponse r = (ApiResponse) t;
            if (!r.isSuccess()) {
                throw new ApiException(r.getErrorCode(), r.getMsg());
            }
        }
        return t;
    }

    protected ApiMarshaller getMarshaller(String format) {
        return MarshallerUtil.getMarshaller(this.format);
    }

    private static String _doPost(String url, String ctype, byte[] content, int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {
        HttpURLConnection conn = null;
        OutputStream out = null;
        String rsp = null;
        try {
            try {
                conn = ApiUtils.getConnection(new URL(url), "POST", ctype, headerMap);
                conn.setConnectTimeout(connectTimeout);
                conn.setReadTimeout(readTimeout);
            } catch (IOException e) {
                throw e;
            }
            try {
                out = conn.getOutputStream();
                out.write(content);
                rsp = ApiUtils.getResponseAsString(conn);
            } catch (IOException e) {
                throw e;
            }
        } finally {
            if (out != null) {
                out.close();
            }
            if (conn != null) {
                conn.disconnect();
            }
        }

        return rsp;
    }

    private static ApiConverter getConvertor(Class<?> fieldType) {
        for (Class<?> aClass : converterMap.keySet()) {
            if (aClass.isAssignableFrom(fieldType)) {
                return converterMap.get(aClass);
            }
        }
        return null;
    }

    /**
     * 调试参数信息，打印到System.out
     *
     * @param debug
     */
    public static void setDebug(boolean debug) {
        OpenApiClient.debug = debug;
    }

}
